package io.noties.markwon.base

import android.content.Context
import android.widget.TextView
import io.noties.markwon.theme.MarkwonCategoryTheme
import org.commonmark.parser.Parser
import java.util.ArrayList

/**
 * Builder for [io.noties.markwon.Markwon].
 *
 *
 * Please note that the order in which plugins are supplied is important as this order will be
 * used through the whole usage of built Markwon instance
 *
 * @since 3.0.0
 */
interface MarkwonBuilder {
    /**
     * Specify bufferType when applying text to a TextView `textView.setText(CharSequence,BufferType)`.
     * By default `BufferType.SPANNABLE` is used
     *
     * @param bufferType BufferType
     */
    fun bufferType(bufferType: TextView.BufferType): MarkwonBuilder

    /**
     * @param textSetter [TextSetter] to apply text to a TextView
     * @since 4.1.0
     */
    fun textSetter(textSetter: Markwon.TextSetter): MarkwonBuilder
    fun usePlugin(plugin: MarkwonPlugin): MarkwonBuilder
    fun usePlugins(plugins: Iterable<MarkwonPlugin>): MarkwonBuilder

    /**
     * Control if small chunks of non-finished markdown sentences (for example, a single `*` character)
     * should be displayed/rendered as raw input instead of an empty string.
     *
     *
     * Since 4.4.0 `true` by default, versions prior - `false`
     *
     * @since 4.4.0
     */
    fun fallbackToRawInputWhenEmpty(fallbackToRawInputWhenEmpty: Boolean): MarkwonBuilder
    fun build(): Markwon
}

/**
 * @since 3.0.0
 */
internal class MarkwonBuilderImpl(private val context: Context, private val theme: MarkwonCategoryTheme) :
    MarkwonBuilder {
    private val plugins: MutableList<MarkwonPlugin> = ArrayList(3)
    private var bufferType = TextView.BufferType.SPANNABLE
    private var textSetter: Markwon.TextSetter? = null

    // @since 4.4.0
    private var fallbackToRawInputWhenEmpty = true
    override fun bufferType(bufferType: TextView.BufferType): MarkwonBuilder {
        this.bufferType = bufferType
        return this
    }

    override fun textSetter(textSetter: Markwon.TextSetter): MarkwonBuilder {
        this.textSetter = textSetter
        return this
    }

    override fun usePlugin(plugin: MarkwonPlugin): MarkwonBuilder {
        plugins.add(plugin)
        return this
    }

    override fun usePlugins(plugins: Iterable<MarkwonPlugin>): MarkwonBuilder {
        val iterator = plugins.iterator()
        var plugin: MarkwonPlugin
        while (iterator.hasNext()) {
            plugin = iterator.next()
            this.plugins.add(plugin)
        }
        return this
    }

    override fun fallbackToRawInputWhenEmpty(fallbackToRawInputWhenEmpty: Boolean): MarkwonBuilder {
        this.fallbackToRawInputWhenEmpty = fallbackToRawInputWhenEmpty
        return this
    }

    override fun build(): Markwon {
        check(plugins.isNotEmpty()) {
            "No plugins were added to this builder. Use #usePlugin " +
                    "method to add them"
        }

        // please note that this method must not modify supplied collection
        // if nothing should be done -> the same collection can be returned
        val plugins = preparePlugins(plugins)
        val parserBuilder = Parser.Builder()
        //final MarkwonTheme.Builder themeBuilder = MarkwonTheme.builderWithDefaults(context);
        val configurationBuilder = MarkwonConfiguration.Builder()
        val visitorBuilder: MarkwonVisitor.Builder = MarkwonVisitorImpl.BuilderImpl()
        val spanFactoryBuilder: MarkwonSpansFactory.Builder = MarkwonSpansFactoryImpl.BuilderImpl()
        for (plugin in plugins) {
            plugin.configureParser(parserBuilder)
            //plugin.configureTheme(themeBuilder);
            plugin.configureConfiguration(configurationBuilder)
            plugin.configureVisitor(visitorBuilder)
            plugin.configureSpansFactory(spanFactoryBuilder)
        }
        val configuration = configurationBuilder.build(theme, spanFactoryBuilder.build())

        // @since 4.1.1
        // @since 4.1.2 - do not reuse render-props (each render call should have own render-props)
        val visitorFactory: MarkwonVisitorFactory = MarkwonVisitorFactory.create(
            visitorBuilder,
            configuration
        )
        return MarkwonImpl(
            bufferType,
            textSetter,
            parserBuilder.build(),
            visitorFactory,
            configuration,
            plugins,
            fallbackToRawInputWhenEmpty
        )
    }

    companion object {
        private fun preparePlugins(plugins: List<MarkwonPlugin>): List<MarkwonPlugin> {
            return MarkwonPluginRegistryImpl(plugins).process()
        }
    }
}